/*
 * HTModelNodeComposite.java
 * www.bouthier.net
 *
 * The MIT License :
 * -----------------
 * Copyright (c) 2001-2003 Christophe Bouthier
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

package hypertree.model;

import java.util.Enumeration;
import java.util.HashSet;
import java.util.Vector;

/**
 * The HTModelNodeComposite class implements the Composite design pattern for
 * HTModelNode. It represents a HTModelNode which is not a leaf.
 * 
 * @author Christophe Bouthier [bouthier@loria.fr]
 * @version 1.0
 */
public class HTModelNodeComposite extends HTModelNode
{

	// private Vector<HTNode> children = null; // children of this node
	private Vector<HTModelNode>		children		= null;					// children
	// of
	// this node

	private double					globalWeight	= 0.0;						// sum
	// of
	private static HashSet<HTNode>	addedNodes		= new HashSet<HTNode>();

	// children

	// weight

	/* --- Constructor --- */

	/**
	 * Constructor for root node.
	 * 
	 * @param node
	 *            the encapsulated HTNode
	 * @param model
	 *            the tree model using this HTModelNode
	 */
	public HTModelNodeComposite(HTNode node, HTModel model)
	{
		this(node, null, model);
	}

	/**
	 * Constructor.
	 * 
	 * @param node
	 *            the encapsulated HTNode
	 * @param parent
	 *            the parent node
	 * @param model
	 *            the tree model using this HTModelNode
	 */
	public HTModelNodeComposite(HTNode node, HTModelNodeComposite parent,
			HTModel model)
	{
		super(node, parent, model);

		this.children = new Vector<HTModelNode>();
		// HTNode childNode = null;
		HTModelNode child = null;

		for (HTNode childNode : node.children())
		{
			if (childNode.isLeaf())
			{
				child = new HTModelNode(childNode, this, model);
				this.addChild(child);
			}
			else
			{
				if (!HTModelNodeComposite.addedNodes.contains(childNode))
				{
					HTModelNodeComposite.addedNodes.add(childNode);
					child = new HTModelNodeComposite(childNode, this, model);
					HTModelNodeComposite.addedNodes.remove(childNode);
					this.addChild(child);
				}
			}

		}

		// here the down of the tree is built, so we can compute the weight
		this.computeWeight();
	}

	/* --- Weight Managment --- */

	/**
	 * Compute the Weight of this node. As the weight is computed with the log
	 * of the sum of child's weight, we must have all children built before
	 * starting the computing.
	 */
	private void computeWeight()
	{
		HTModelNode child = null;

		for (Enumeration<HTModelNode> e = this.children(); e.hasMoreElements();)
		{
			child = e.nextElement();
			this.globalWeight += child.getWeight();
		}
		if (this.globalWeight != 0.0)
		{
			this.weight += Math.log(this.globalWeight);
		}
	}

	/* --- Tree management --- */

	/**
	 * Returns the children of this node, in an Enumeration.
	 * 
	 * @return the children of this node
	 */
	public Enumeration<HTModelNode> children()
	{
		return this.children.elements();
	}

	/**
	 * Adds the HTModelNode as a children.
	 * 
	 * @param child
	 *            the child
	 */
	public void addChild(HTModelNode child)
	{
		this.children.addElement(child);
	}

	/**
	 * Returns <CODE>false</CODE> as this node is an instance of
	 * HTModelNodeComposite.
	 * 
	 * @return <CODE>false</CODE>
	 */
	@Override
	public boolean isLeaf()
	{
		return false;
	}

	/* --- Hyperbolic layout --- */

	/**
	 * Layout this node and its children in the hyperbolic space. Mainly, divide
	 * the width angle between children and put the children at the right angle.
	 * Compute also an optimized length to the children.
	 * 
	 * @param sector
	 *            the sector
	 * @param length
	 *            the parent-child length
	 */
	@Override
	public void layout(HTSector sector, double length)
	{
		super.layout(sector, length);

		if (this.parent != null)
		{
			sector.translate(this.parent.getCoordinates());
			HTCoordE h = new HTCoordE(this.getCoordinates());
			h.x = -h.x;
			h.y = -h.y;
			sector.translate(h);
		}

		int nbrChild = this.children.size();
		double l1 = (0.95 - this.model.getLength());
		double l2 = Math.cos((20.0 * Math.PI) / (2.0 * nbrChild + 38.0));
		length = this.model.getLength() + (l1 * l2);

		double alpha = sector.getAngle();
		double omega = sector.A.arg();
		HTCoordE K = new HTCoordE(sector.A);

		// It may be interesting to sort children by weight instead
		for (Enumeration<HTModelNode> e = this.children(); e.hasMoreElements();)
		{
			HTModelNode child = e.nextElement();
			HTSector childSector = new HTSector();
			childSector.A = new HTCoordE(K);
			omega += (alpha * (child.getWeight() / this.globalWeight));
			K.x = Math.cos(omega);
			K.y = Math.sin(omega);
			childSector.B = new HTCoordE(K);
			child.layout(childSector, length);
		}

	}

	/* --- ToString --- */

	/**
	 * Returns a string representation of the object.
	 * 
	 * @return a String representation of the object
	 */
	@Override
	public String toString()
	{
		String result = super.toString() + "\n\tChildren :";
		for (HTModelNode currentChild : this.children)
		{
			result += "\n\t-> " + currentChild.getName();
		}
		return result;
	}

}
